package org.budo.support.spring.context.aware;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.budo.support.lang.util.StringUtil;
import org.budo.support.slf4j.Slf4j;
import org.budo.support.spring.util.SpringUtil;
import org.slf4j.Logger;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;

/**
 * @author lmw
 */
public class BudoApplicationContextAware extends AbstractApplicationContextAware {
    private static final Logger log = Slf4j.getLogger();

    private static BudoApplicationContextAware _INSTANCE = null;

    private final Map<Class<?>, Object> _beanInstanceCacheMap = new ConcurrentHashMap<Class<?>, Object>();

    private List<Throwable> initCallers = new ArrayList<Throwable>();

    public BudoApplicationContextAware() {
        synchronized (BudoApplicationContextAware.class) {
            if (null == _INSTANCE) {
                _INSTANCE = this;

                _INSTANCE.initCallers.add(new Throwable("#32 BudoApplicationContextAware init stackTrace"));
                log.info("#33 initing BudoApplicationContextAware, initCallers=" + StringUtil.join(_INSTANCE.initCallers));
            } else {
                _INSTANCE.initCallers.add(new Throwable("#35 BudoApplicationContextAware init stackTrace"));
                log.error("#36 initing BudoApplicationContextAware many times, initCallers=" + StringUtil.join(_INSTANCE.initCallers));

                if (log.isDebugEnabled()) {
                    for (Throwable each : _INSTANCE.initCallers) {
                        log.debug("#41 initCaller=" + each, each);
                    }
                }
            }
        }
    }

    public static BudoApplicationContextAware getInstance() {
        return _INSTANCE;
    }

    public static BudoApplicationContextAware getInstanceNoNull() {
        if (null == _INSTANCE) {
            throw new RuntimeException("#55 please conf org.budo.support.spring.context.aware.BudoApplicationContextAware as a spring bean");
        }

        return _INSTANCE;
    }

    public static <T> T getBean(Class<T> requiredType) {
        BudoApplicationContextAware instance = getInstanceNoNull();
        ApplicationContext applicationContext = instance.getApplicationContext();
        return applicationContext.getBean(requiredType);
    }

    public static Object getBean(String beanName) {
        BudoApplicationContextAware instance = getInstanceNoNull();
        ApplicationContext applicationContext = instance.getApplicationContext();
        return applicationContext.getBean(beanName);
    }

    /**
     * 获取Bean，如果 BudoApplicationContextAware 为空，则返回空
     */
    public static <T> T getBeanCached_2(Class<T> requiredType) {
        BudoApplicationContextAware applicationContextAware = getInstance();

        if (null == applicationContextAware) {
            return null;
        }

        Map<Class<?>, Object> instanceMap = applicationContextAware._beanInstanceCacheMap;
        Object beanInstance = instanceMap.get(requiredType);

        if (null != beanInstance) {
            return (T) beanInstance;
        }

        beanInstance = SpringUtil.getBean(applicationContextAware.getApplicationContext(), requiredType);
        if (null == beanInstance) {
            return null;
        }

        instanceMap.put(requiredType, beanInstance);
        return (T) beanInstance;
    }

    /**
     * 不会返回空
     */
    public static <T> T getBeanCached(Class<T> requiredType) {
        BudoApplicationContextAware instance = getInstanceNoNull();

        Map<Class<?>, Object> instanceMap = instance._beanInstanceCacheMap;
        Object beanInstance = instanceMap.get(requiredType);

        if (null != beanInstance) {
            return (T) beanInstance;
        }

        beanInstance = getBean(requiredType);
        instanceMap.put(requiredType, beanInstance);
        return (T) beanInstance;
    }

    @Override
    public String toString() {
        return super.toString() + ", initCallers=" + StringUtil.join(initCallers);
    }

    public static void _setApplicationContext(ApplicationContext applicationContext) {
        String[] beanNames = applicationContext.getBeanNamesForType(BudoApplicationContextAware.class);
        if (null != beanNames && beanNames.length > 0) {
            log.warn("#21 _setApplicationContext, applicationContext 中已经配置了 BudoApplicationContextAware, applicationContext=" + applicationContext);
            return;
        }

        log.error("#24 _setApplicationContext, 请配置一个 BudoApplicationContextAware 的Bean, applicationContext=" + applicationContext);

        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        BeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClassName(BudoApplicationContextAware.class.getName());
        beanFactory.registerBeanDefinition("BudoApplicationContextAware", beanDefinition);

        log.warn("#33 registerBeanDefinition BudoApplicationContextAware, applicationContext=" + applicationContext);
    }

    public static <T> Map<String, T> _getBeansOfType(Class<T> type) {
        BudoApplicationContextAware applicationContextAware = BudoApplicationContextAware.getInstanceNoNull();
        ApplicationContext applicationContext = applicationContextAware.getApplicationContext();
        return applicationContext.getBeansOfType(type);
    }

    public static String getNameOfBean(Object beanInstance) {
        BudoApplicationContextAware applicationContextAware = getInstanceNoNull();
        ApplicationContext applicationContext = applicationContextAware.getApplicationContext();
        return SpringUtil.getNameOfBean(applicationContext, beanInstance);
    }
}
